
#import "EMErrorManager.h"
#import "EMErrorObserver.h"
#import "EMErrorDescription.h"
#import "EMObjcErrorCatcher.h"
#import "localizable_strings.h"
#import <appkit/appkit.h>

#define ALIGNMENT(type) (offsetof(struct { char c; type t; },t))

@implementation EMErrorManager

static id theEMErrorManager = nil;

//--------------------------------------------------------------------------------
// HILFSFUNKTIONEN
// Die ErrorReporter und -handler Funktionen, SignalHandler
//--------------------------------------------------------------------------------

static void EMErrorReporter(NXHandler *errorState)
	{
	EMErrorInfo	*ex=(id)errorState->data1;

	NXLogError("EMErrorReporter: %s: #%d\n",[ex location],[ex code]);
	}


static void EMErrorHandler(NXHandler *errorState)
	{
	if(![[EMErrorManager new] handle:EMInfoObjectForHandler(errorState)])
		NXDefaultTopLevelErrorHandler(errorState);
	}


static void EMSignalHandler(int signalno)
	{
	sigsetmask(0);  // unblock all signals...
	EM_ERROR(EM_SIGBASE+signalno,NULL,NULL);
	}


//--------------------------------------------------------------------------------
// HILFSFUNKTIONEN, Teil 2
//--------------------------------------------------------------------------------

EMErrorInfo *EMInfoObjectForHandler(NXHandler *errorState)
	{
	EMErrorInfo 						*exception;
	const struct { @defs(EMErrorInfo) }	*eip;

	if(errorState->data1!=NULL && (((int)(errorState->data1))%ALIGNMENT(id))==0 && 
		((eip=errorState->data1)->code==errorState->code))
		exception=(id)errorState->data1;
	else
		exception=[[EMErrorInfo newWithCode:errorState->code 
				userInfo:errorState->data1:errorState->data2] getContext];
	return exception;	
	}

//	if(errorState->code>=NX_APP_ERROR_BASE && 
//		errorState->code<NX_APP_ERROR_BASE+EM_ERROR_RANGE)

//--------------------------------------------------------------------------------
// HILFSFUNKTIONEN, Teil 3
//--------------------------------------------------------------------------------

/*
static void EMFreeValues(void *val)
	{
	id	v=val;
	
	if([v isKindOf:[List class]])
		[v freeObjects];
	[v free];
	}
*/

//--------------------------------------------------------------------------------
// KLASSENMETHODEN
// Sicherstellen, dass nur eine Instanz benutzt wird.
//--------------------------------------------------------------------------------

+ new
	{
	if(!theEMErrorManager)
		{
		NXZone *myZone=NXCreateZone(vm_page_size*4,vm_page_size*2,YES);

		NXNameZone(myZone,"EMErrorManager");
		theEMErrorManager=self=[[super allocFromZone:myZone] init];
		}
	else 
		self=theEMErrorManager;
	return self;
	}	


//--------------------------------------------------------------------------------
// INIT & FREE
//--------------------------------------------------------------------------------

- init;
	{
	[super init];
	[self readErrorDescriptions];
	objectsToNotify=[[HashTable allocFromZone:[self zone]] 
							initKeyDesc:"i" valueDesc:"@"];
	[EMObjcErrorCatcher setup];
	[self installErrorReporter:YES];
	[self installErrorHandler:YES];
	[self installSignalHandler:YES];
	return self;
	}
	

- free;
	{
//	[objectsToNotify freeKeys:NULL values:EMFreeValues];
//  allgemeines Porblem: wann duerfen die EMErrorObserver freigegeben werden?
	[objectsToNotify free];
	return [super free];
	}


//--------------------------------------------------------------------------------
// ERROR FILE
//--------------------------------------------------------------------------------

- readErrorDescriptions;
	{
	return [self readErrorDescriptionsFromBundle:[NXBundle mainBundle]];
	}


- readErrorDescriptionsFromBundle:(NXBundle *)bundle;
	{
	char				path[MAXPATHLEN+1];
	struct stat			buf;
	NXTypedStream		*stream;
	int					vmajor,vemc;
	HashTable			*newTable;
	NXHashState  		state;
	const void  		*key; 
	EMErrorDescription	*ed;

	NX_DURING
		[bundle getPath:path forResource:"Errors" ofType:"estore"];
		if(strlen(path)<3)
			NX_RAISE(NX_APP_ERROR_BASE,CANT_LOCATE_MSGS,NULL);
		else if(stat(path,&buf))
			NX_RAISE(NX_APP_ERROR_BASE,CANT_ACCESS_MSGS,NULL);
		else if(!(buf.st_mode && S_IFREG))
			NX_RAISE(NX_APP_ERROR_BASE,CANT_LOCATE_MSGS,NULL);
		stream=NXOpenTypedStreamForFile(path,NX_READONLY);
		NXSetTypedStreamZone(stream,[self zone]);
		NXReadTypes(stream,"ii",&vmajor,&vemc);
		if(vmajor*100+vemc > EM_MAJOR_VERSION*100+EM_EMC_VERSION)
			NX_RAISE(NX_APP_ERROR_BASE,MSG_NEW_VERSION,NULL);
		else if(vmajor*100+vemc < EM_MAJOR_VERSION*100+EM_EMC_VERSION)
			NX_RAISE(NX_APP_ERROR_BASE,MSG_OLD_VERSION,NULL);
		newTable=NXReadObject(stream);
		NXCloseTypedStream(stream);
		if(errorDescriptions==nil)
			errorDescriptions=newTable;
		else
			{
			state=[newTable initState];
			while([newTable nextState:&state key:&key value:(void *)&ed]) 
				{
				if([errorDescriptions isKey:key])
					NX_RAISE(NX_APP_ERROR_BASE,ERROR_NUMBER_CLASH,NULL);
		   		[errorDescriptions insertKey:key value:ed];
				}
			[newTable free];
			}
	NX_HANDLER
		if([self errorDescriptionFor:EM_INTBASE+2])
			EM_ERROR(EM_INTBASE+2,NXLocalHandler.data1,NULL);
		else
			{ 
			NXRunAlertPanel(INTERNAL_ERROR,CANT_LOAD_MESSAGES,QUIT_BUTTON,NULL,NULL,
						NXLocalHandler.data1,EM_MAJOR_VERSION,EM_EMC_VERSION);
			exit(1);
			}
	NX_ENDHANDLER
	
	return self;
	}


- (HashTable *)errorDescriptions;
	{
	return errorDescriptions;
	}
	

- (EMErrorDescription *)errorDescriptionFor:(int)code;
	{
	return (EMErrorDescription *)[errorDescriptions valueForKey:(void *)code];
	}


//--------------------------------------------------------------------------------
// Handler (de)installieren
//--------------------------------------------------------------------------------

- installErrorReporter:(BOOL)flag;
	{
	if(flag)
		NXRegisterErrorReporter(NX_APPBASE,NX_APPBASE+EM_ERROR_RANGE,
				EMErrorReporter);
	else
		NXRemoveErrorReporter(NX_APPBASE);
	return self;
	}


- installErrorHandler:(BOOL)flag;
	{
	if(flag)
		NXSetTopLevelErrorHandler(EMErrorHandler);
	else
		NXSetTopLevelErrorHandler(NXDefaultTopLevelErrorHandler);
	return self;
	}


- installSignalHandler:(BOOL)flag;
	{
	BOOL 	signals[]={1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,-1};
	int		i;
	void (*handler)(int);

	handler=flag?EMSignalHandler:SIG_DFL;
	for (i=0;signals[i]!=-1;i++)
		if(signals[i])  
			signal(i,handler);
	return self;
	}


//--------------------------------------------------------------------------------
// Raise Error
//--------------------------------------------------------------------------------

- raise:infoObject;
	{
	[infoObject getContext];
	switch([[self errorDescriptionFor:[infoObject code]] severity])
		{
	case EMSeverityWarning: 
		[self handle:infoObject];
		break;
	case EMSeverityError:
		NX_RAISE([infoObject code],infoObject,NULL);
		break;
	case EMSeverityFatalError:
		[self handle:infoObject];
		exit(2);
		break;
	case EMSeverityUnknown:
		if(([infoObject code])==EM_INTBASE+1)
			{
			NXRunAlertPanel(NULL,NO_UNKNOWN_EVEN,QUIT_BUTTON,NULL,NULL,
				[infoObject codeString]);
			exit(2);
			}
		EM_ERROR(EM_INTBASE+1,(void *)[infoObject code],[infoObject location]);
		break;
		}
	return self;
	}

//--------------------------------------------------------------------------------
// Fehler behandeln
//--------------------------------------------------------------------------------

- handle:infoObject;
	{
	id	ed=[self errorDescriptionFor:[infoObject code]],
		eo=[objectsToNotify valueForKey:(void *)[infoObject code]];

	[self notify:[ed actions] of:infoObject];
	[self notify:eo of:infoObject];
	[NXApp delayedFree:infoObject];
	return ed;
	}


//--------------------------------------------------------------------------------
// Objekte ueber den Fehler benachrichtigen
//--------------------------------------------------------------------------------

- notify:obj of:infoObject;
	{
	if([obj isKindOf:[List class]])
		[obj makeObjectsPerform:@selector(dispatch:) with:infoObject];
	else
		[obj perform:@selector(dispatch:) with:infoObject];
	return self;
	}
	

//--------------------------------------------------------------------------------
// Objekte registrieren, die im Fehlerfall benachrichrichtigt werden
//--------------------------------------------------------------------------------

- addObserver:anObject selector:(SEL)aSel forError:(int)errorNumber;
	{
	return [self addObserver:anObject selector:aSel forErrors:errorNumber:1];
	}
	

- addObserver:anObject selector:(SEL)aSel forErrors:(int)base:(int)count;
	{
	id	observer,entry;
	int	i;
	
	NX_DURING
		observer=[EMErrorObserver allocFromZone:[self zone]];
		[observer initWith:anObject andSelector:aSel];
	NX_HANDLER
		[observer free];
		NX_RERAISE();
	NX_ENDHANDLER
	
	for(i=base;i<base+count;i++)
		{
		entry=[objectsToNotify valueForKey:(void *)i];

		if(!entry)
			[objectsToNotify insertKey:(void *)i value:observer];
		else if(![entry isKindOf:[List class]])
			[objectsToNotify insertKey:(void *)i
				value:[[[[List allocFromZone:[self zone]] 
								init] addObject:entry] addObject:observer]];
		else
			[entry addObject:observer];
		}
	return self;
	}
	
	
- removeObserver:anObject forError:(int)errorNumber
	{
	return [self removeObserver:anObject forErrors:errorNumber:1];
	}


- removeObserver:anObject forErrors:(int)base:(int)count;
	{
	id	entry;
	int	i,j;

	for(i=base;i<base+count;i++)
		{
		entry=[objectsToNotify valueForKey:(void *)i];
	
		if(!entry)
			EM_ERROR(EM_INTBASE+12,(void *)i,NULL);
		else if(![entry isKindOf:[List class]])
			if([entry observer]==anObject)
				[objectsToNotify removeKey:(void *)i];
			else
				EM_ERROR(EM_INTBASE+12,(void *)i,NULL);
		else     
			{
			for(j=[entry count]-1;j>=0;j--)
				if([[entry objectAt:j] observer]==anObject)
					break;
			if(j>=0)
				{
				[entry removeObjectAt:j];
				if([entry count]<2)
					{
					[objectsToNotify insertKey:(void *)i value:[entry lastObject]];
					[entry free];
					}
				}
			else
				EM_ERROR(EM_INTBASE+12,(void *)i,NULL);
			}
		}
	return self;
	}
	

//--------------------------------------------------------------------------------
// THAT'S IT
//--------------------------------------------------------------------------------

@end
